package com.sinopec.siam.agent.web;

import com.sinopec.siam.agent.common.*;
import com.sinopec.siam.agent.sp.config.*;
import com.sinopec.siam.agent.sp.config.entity.AuthenLevelMethodEntity;
import com.sinopec.siam.agent.sp.handler.*;
import com.sinopec.siam.agent.spi.PostAuthenticationCallback;
import com.sinopec.siam.agent.spi.impl.WebSEALEAIPostAuthenHandler;
import com.sinopec.siam.am.common.profile.AbstractErrorHandler;
import com.sinopec.siam.am.common.profile.ProfileException;
import com.sinopec.siam.am.common.profile.ProfileHandler;
import com.sinopec.siam.apache.log4j.Logger;
import com.sinopec.siam.javax.xml.parsers.ParserConfigurationException;
import com.sinopec.siam.opensaml.ws.transport.http.HTTPInTransport;
import com.sinopec.siam.opensaml.ws.transport.http.HTTPOutTransport;
import com.sinopec.siam.opensaml.ws.transport.http.HttpServletRequestAdapter;
import com.sinopec.siam.opensaml.ws.transport.http.HttpServletResponseAdapter;
import com.sinopec.siam.opensaml.xml.util.DatatypeHelper;
import com.sinopec.siam.utils.StringUtils;
import com.sinopec.siam.w3c.dom.Element;
import com.sinopec.siam.w3c.dom.NodeList;
import weaver.hrm.User;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.net.URL;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;


/**
 * Servlet Filter implementation class SAMLProfileFilter
 */
public class AccessEnforcer implements Filter {

    private static Logger log = Logger.getLogger(AccessEnforcer.class);
    private static final String spSimpleConfigFile = "spSimpleConfigFile";

    public static final String ATTR_NAME_AUTHEN_REQUEST_CONTEXT = "__AGENT_AUTHEN_REQUEST_CONEXT";

    private String LOGIN_USERS = "";

    /**
     * 总是要求IdP做重新认证.
     */
    private boolean alwaysReauthen = false;

    public static ContextRepository contextRepository;
    private String childContextRepository = "";

    /**
     * 不需要拦截URL.
     */
    private String[] noFilterURLs = null;

    /**
     * 当前认证方式
     */
    String currentAuthenMethod="";

    /**
     * EAI认证.
     */
    private boolean eaiAuthen = false;

    /**
     * CookieDomain
     */
    private static String cookieDomain = null;

    /**
     * WebSEAL EAI parameter: WebSEAL will set this parameter into http request, and request EAI challenge user for specified authen level.
     */
    private String AUTHN_LEVEL_FIELD  = "AUTHNLEVEL";

    static {
        java.security.Security.addProvider(new com.sinopec.siam.bouncycastle.jce.provider.BouncyCastleProvider());
    }


    /**
     * Default constructor.
     */
    public AccessEnforcer() {
        super();
    }

    /**
     * @see Filter#init(FilterConfig)
     */
    public void init(FilterConfig fConfig) throws ServletException {
        try {
            ServletContext ctx = fConfig.getServletContext();
            String spSimpleConfigFilePath = ctx.getInitParameter(spSimpleConfigFile);
            //从web.xml获取日志配置文件路径
//	    LogManager.customizedPath(spSimpleConfigFilePath);
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>1.获取配置文件地址="+spSimpleConfigFilePath);
//	    LogFactory.initWorkLog(spSimpleConfigFilePath);
            SysConfig.initConfig(spSimpleConfigFilePath);
            validateConfig();
            new WebApplicationContextUtils().initObjects();
            initAuthenLevel();
            initKeyStore();
            LOGIN_USERS = SysConfig.login_users;
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>白名单过滤人员为="+SysConfig.login_users);
            currentAuthenMethod = "urn:oasis:names:tc:SAML:2.0:ac:classes=" + SysConfig.sp_auth_method;
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>7.当前设置认证方式为="+SysConfig.sp_auth_method);
            String t = fConfig.getInitParameter("AlwaysReauthen");
            this.alwaysReauthen = (t == null)?false:Boolean.valueOf(t).booleanValue();
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>8.总是要求IdP做重新认证="+this.alwaysReauthen);
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>"+StringUtils.format("[%s]:Always Re-authen {[%s]}", new Object[]{fConfig.getFilterName(), new Boolean(this.alwaysReauthen)}));
            t = fConfig.getInitParameter("noFilterURLs");
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>9.不需要拦截URL="+t);
            this.noFilterURLs = (t == null) ? null : t.split(";");
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>"+StringUtils.format("[%s]:noFilterURLs {[%s]}", new Object[]{fConfig.getFilterName(), t}));

            t = fConfig.getInitParameter("eaiAuthen");
            this.eaiAuthen = (t == null)?false:Boolean.valueOf(t).booleanValue();
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>10.是否eai认证="+this.eaiAuthen);
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>"+StringUtils.format("[%s]:EAI Authen {[%s]}", new Object[]{fConfig.getFilterName(), new Boolean(this.eaiAuthen)}));
            cookieDomain = fConfig.getInitParameter("cookieDomain");
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>11.CookieDomain"+cookieDomain);
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>"+StringUtils.format("[%s]:Cookie Domain {[%s]}", new Object[]{fConfig.getFilterName(), cookieDomain}));

            t = fConfig.getInitParameter("AUTHN_LEVEL_FIELD");
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>12.设置EAI的认证级别属性="+t);
            this.AUTHN_LEVEL_FIELD = (t != null && t.trim().length() > 0)?t:"AUTHNLEVEL";
            log.info(">>>>>>>>>>>>>>>>>>启动完成>>>>>>>>>>>>>>>>>>>>>");
            log.info(">>>>>AccessEnforcer>>>>>init>>>>>session_local="+SysConfig.session_local);
            if("true".equals(SysConfig.session_local)){
                contextRepository = new HttpSessionContent();
            }else{
                loadChildren();
                log.info(">>>>>AccessEnforcer>>>>>init>>>>>childContextRepository="+childContextRepository);
                if(!"".equals(childContextRepository)){
                    try {
                        Class<?> cls = Class.forName(childContextRepository);
                        Constructor<?> cons = cls.getConstructor();
                        contextRepository = (ContextRepository) cons.newInstance();
                    } catch (Exception e) {
                        log.info(">>>>>AccessEnforcer>>>>>init>>>>>reidsContext new instance fail ,place chek config");
                        e.printStackTrace();
                        contextRepository = new HttpSessionContent();
                    }
                }else{
                    log.error(">>>>>AccessEnforcer>>>>>init>>>>>can not find reidsContext,place chek config");
                }
            }
        } catch (Exception e) {
            if(log == null){
                e.printStackTrace();
            }else{
                log.error(">>>>>AccessEnforcer>>>>>init>>>>>"+StringUtils.format("初始化失败：[%s]",new Object[]{e.getMessage()}),e);
            }

        }
    }

    private void loadChildren() {
        Class<?> clazz = AbstractContextRepository.class;
        // 定位到bin目录
        URL url = clazz.getResource("/");

        String fileName = url.getFile();

        String pathName = fileName.replaceFirst("/", "");

        File rootFile = new File(pathName);

        setSubList(rootFile, rootFile.getPath() + "\\", clazz);
    }
    public <T> void setSubList(File rootFile, String parentDirectory,
                               Class<T> parentClass) {
        if (rootFile.isDirectory()) {
            File[] files = rootFile.listFiles();
            for (File file : files) {
                setSubList(file, parentDirectory, parentClass);
            }
        } else {
            String className = null;
            try {
                if (rootFile.getPath().indexOf(".class") != -1) {
                    className = rootFile.getPath().replace(parentDirectory, "")
                            .replace(".class", "").replace("\\", ".");
                    Class<?> classObject = Class.forName(className);
                    classObject.asSubclass(parentClass);
                    // 要么是子类，要么是类本身
                    if (!className.equals(parentClass.getCanonicalName())){
                        childContextRepository = className;
                    }
                }
            } catch (ClassNotFoundException e) {
                System.err.println("can not find " + className);
            } catch (ClassCastException e) {
                System.err.println(className + " do not extends " + parentClass);
            }
        }
    }
    /**
     * 检验配置项
     *
     **/
    private void validateConfig() throws IllegalArgumentException, IllegalAccessException {
        log.info(">>>>>AccessEnforcer>>>>>validateConfig>>>>>3.检测配置文件输出空白项");
        Field[] fields = SysConfig.class.getFields();
        for (int i = 0; i < fields.length; i++) {
            Field f = fields[i];
            String fieldName = f.getName();
            Object object = f.get(fieldName);
            if (object instanceof String) {
                String obj = (String) object;
                if(StringUtils.isEmpty(obj)){
                    log.info(">>>>>AccessEnforcer>>>>>validateConfig>>>>>"+StringUtils.format("选项 [%s]为空",new Object[]{fieldName}));
                }else{
                    log.info(">>>>>AccessEnforcer>>>>>validateConfig>>>>>"+fieldName + " = " + obj);
                }
            }
        }

    }

    /**
     * 初始化keystore
     * **/
    private void initKeyStore(){
        // DecryptorResolverImpl decryptorResolverImpl = new DecryptorResolverImpl();
        try {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>6.初始化keystore");
            new DecryptorResolverImpl().initialize();
        } catch (KeyStoreException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>KeyStoreException",e);
        } catch (ProfileException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>ProfileException",e);
        } catch (NoSuchAlgorithmException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>NoSuchAlgorithmException",e);
        } catch (CertificateException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>CertificateException",e);
        }  catch (IOException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>IOException",e);
        } catch (UnrecoverableKeyException e) {
            log.info(">>>>>AccessEnforcer>>>>>initKeyStore>>>>>UnrecoverableKeyException",e);
        }
    }

    /***
     * 初始化认证级别
     */
    private void initAuthenLevel(){
        //ServletContext ctx = getServletContext();
        String configPath = SysConfig.sp_auth_method_level_file;
        try {
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>5.初始化认证级别");
            String authLevelFile = configPath;
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>5.1读取认证级别配置文件:authLevelFile="+authLevelFile);
            InputStream alIs = Tool.getResourceInputStream(authLevelFile);
            setAuthenLevel(XmlTool.getXmlDocument(alIs).getDocumentElement());
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>5.2将认证方式和认证级别存入AuthenLevelDirectorXmlImpl中");
            Tool.releaseInputStream(alIs);
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>5.3关闭流");
        } catch (FileNotFoundException e) {
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>FileNotFoundException",e);
        } catch (ParserConfigurationException e) {
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>ParserConfigurationException",e);
        } catch (Exception e) {
            log.info(">>>>>AccessEnforcer>>>>>initAuthenLevel>>>>>Exception",e);
        }

    }

    private void setAuthenLevel(Element e) {
        AuthenLevelDirectorXmlImpl AuthenLevelDirector = new AuthenLevelDirectorXmlImpl();
        List authenLevelMethodEntitys = new ArrayList();
        NodeList tmpList = XmlTool.getElementsByLocalName(e, "authenMethodEntity");
        for (int i = 0; i < tmpList.getLength(); i++) {
            AuthenLevelMethodEntity authenLevelMethodEntity = new AuthenLevelMethodEntity();
            Element authenMethodEntityElem = (Element) tmpList.item(i);
            NodeList authLevelList = XmlTool.getElementsByLocalName(authenMethodEntityElem, "authLevel");
            NodeList authenMethodsList = XmlTool.getElementsByLocalName(authenMethodEntityElem, "authenMethods");
            Element authLevelsElem = (Element) authLevelList.item(0);
            String authLevel = XmlTool.getTextContent(authLevelsElem).trim();

            NodeList authenMethodList = XmlTool.getElementsByLocalName((Element) authenMethodsList.item(0), "authenMethod");
            List list = new ArrayList();
            for (int j = 0; j < authenMethodList.getLength(); j++) {
                Element authenMethodElem = (Element) authenMethodList.item(j);
                String authenMethod = XmlTool.getTextContent(authenMethodElem).trim();
                list.add(authenMethod);
            }
            authenLevelMethodEntity.setAuthenLevel(authLevel);
            authenLevelMethodEntity.setAuthenMethods(list);
            authenLevelMethodEntitys.add(authenLevelMethodEntity);
        }
        AuthenLevelDirector.setAuthenLevelMethodEntitys(authenLevelMethodEntitys);
    }

    /**
     * @see Filter#destroy()
     */
    public void destroy() {

    }

    /**
     * @see Filter#doFilter(ServletRequest, ServletResponse, FilterChain)
     */
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException,
            ServletException {
        log.info(">>>>>>>>>>>AccessEnforcer>>>>>>>>>doFilter>>>>>>>>>>>>>>开始执行>>>>>>>>>>>");
        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        if(!"true".equals(SysConfig.session_local)){
            String saml_session_id = CookieUtils.getCookie(httpRequest, CookieUtils.SAML_SP_ID);
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>saml_session_id="+saml_session_id);
            if(StringUtils.isEmpty(saml_session_id)){
                ContextHolder.refreshSamlSessionId();
            }else{
                ContextHolder.setSamlSessionId(saml_session_id);
            }
            CookieUtils.setCookie(httpResponse, CookieUtils.SAML_SP_ID,ContextHolder.getSamlSessionId(), true);
        }
        Date start = null;
        Date end = null;
        start = new Date();
        log.info(">>>>>>>>>>>AccessEnforcer>>>>>>>>>doFilter>>>>>>>>>>>>>>SP拦截访问开始>>>>>>>>>>>");
        WebUtils.debuggerwriteparam(httpRequest,httpResponse);
        log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>1.判断请求是否为受保护资源:"+isNoFilterURLRequest(httpRequest));
        if (isNoFilterURLRequest(httpRequest)) {
            // Handled by SPProfileHandler, do SAML processing(include: SAML and DS)
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>"+StringUtils.format("request uri [%s]: no need filter, do filter chain. this request is finished", new Object[]{httpRequest.getRequestURI().toString()}));
            chain.doFilter(request, response);
            return;
        }
        log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>2.判断是否为单点请求:"+isSPHandlerRequest(httpRequest));
        if (isSPHandlerRequest(httpRequest)) {
            // Handled by SPProfileHandler, do SAML processing(include: SAML and DS)
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>"+StringUtils.format("request uri [%s]: no match with handler URLs, do filter chain. this filter pass", new Object[]{httpRequest.getRequestURI().toString()}));
            chain.doFilter(request, response);
            return;
        }
        SamlSession samlsession = contextRepository.getContext(httpRequest);
        ContextHolder.setContext(samlsession);
        // ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
        // AuthenLevelDirector authenLevelDirector = (AuthenLevelDirector)applicationContext.getBean("siam.sp.authenLevelDirector", AuthenLevelDirector.class);
        AuthenLevelDirector authenLevelDirector = new AuthenLevelDirectorXmlImpl();
        log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>"+StringUtils.format("Need to authenticate: authenLevelDirector [%s].", new Object[]{authenLevelDirector}));
        // Need to authenticate?
        samlsession.setAttribute("authenLevel", currentAuthenMethod);
        log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>3.判断是否需要进行认证"+needToAuthenticate(httpRequest, authenLevelDirector));
        if (needToAuthenticate(httpRequest, authenLevelDirector)) {
//      log.info("(doFilter) - 2.需要认证 ......");
            // Track return URL to application
            SAML2AuthRequestHandler.setLastAccessURL(httpRequest);
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>3.1session中设置请求的url和参数");
            SPProfileHandlerManager handlerManager = (SPProfileHandlerManager) WebApplicationContextUtils.getBean("siam.sp.handler.manager");
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>3.2获取sp总的处理器");
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>"+StringUtils.format("Need to authenticate: handlerManager [%s].", new Object[]{handlerManager}));
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>3.3发送认证请求");
            doAuthentication(httpRequest, httpResponse, handlerManager, authenLevelDirector, currentAuthenMethod, this.AUTHN_LEVEL_FIELD, this.alwaysReauthen);
            contextRepository.saveContext(httpRequest, ContextHolder.getContext());
            ContextHolder.clearContext();
            end = new Date();
            long time = start == null ? 0 :(end.getTime() - start.getTime());
            log.info(">>>>>>>>>>>AccessEnforcer>>>>>>>>>doFilter>>>>>>>>>>>>>>SP拦截访问结束,总耗时=["+time+"]秒>>>>>>>>>>>111");
            return;
        }

//    log.info("(doFilter) - 2.认证成功.");

        // Already authentication, call post
        try {
            // Set return params for WebSEAL EAI
            //PostAuthenticationCallback handler = (PostAuthenticationCallback)applicationContext.getBean("siam.sp.webseal.eai.handler");
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>4.是否需要EAI认证:"+this.eaiAuthen);
            if (this.eaiAuthen){
                log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>执行EAI认证");
                PostAuthenticationCallback handler = new WebSEALEAIPostAuthenHandler();
                handler.handle(httpRequest, httpResponse);
            }
        } catch (Exception e) {
            log.info(">>>>>AccessEnforcer>>>>>doFilter>>>>>"+StringUtils.format("Fail to execute Post-Authenticatoin handler, cause: [%s]", new Object[]{e.getMessage()}), e);
        }

        // Authenticated and matched authen method, do business
        chain.doFilter(request, response);
        end = new Date();
        long time = start == null ? 0 :(end.getTime() - start.getTime());
        log.info(">>>>>>>>>>>AccessEnforcer>>>>>>>>>doFilter>>>>>>>>>>>>>>SP拦截访问结束,总耗时=["+time+"]秒>>>>>>>>>>>222");
        //httpRequest.getSession(true).removeAttribute("weaver_user@bean");
        contextRepository.saveContext(httpRequest, ContextHolder.getContext());
        ContextHolder.clearContext();
        return;
    }

    /**
     * @param httpRequest
     * @param httpResponse
     * @param handlerManager
     * @param authenLevelDirector
     * @param defaultAuthenticationMethod
     * @param authenLevelHeaderName
     * @param alwaysReauthen
     */
    private void doAuthentication(HttpServletRequest httpRequest, HttpServletResponse httpResponse,
                                  SPProfileHandlerManager handlerManager, AuthenLevelDirector authenLevelDirector,
                                  String defaultAuthenticationMethod, String authenLevelHeaderName, boolean alwaysReauthen) {
    	log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.做认证 ......");
        setRequiredAuthenLevel(authenLevelHeaderName, httpRequest);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.1如果有认证等级，将认证等级放入session中");
        setRequiredTheme(ThemeDirector.THEME_PARAM_NAME, httpRequest);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.2如果请求中有IDP主题，将主题放入session中");
        // Send SAML authen request.
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.3获取认证方法");
        String requiredAuthenticationMethod = getRequiredAuthenMethod(httpRequest, authenLevelDirector, defaultAuthenticationMethod, authenLevelHeaderName);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>requiredAuthenticationMethod="+requiredAuthenticationMethod);
//    	log.info(com.sinopec.siam.utils.StringUtils.format("(doAuthentication) - 4.认证方法:[%s].", new Object[]{requiredAuthenticationMethod}));
        String themeOfIdPLoginPage = getRequiredTheme();
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.3.3获取认证页面主题:"+themeOfIdPLoginPage);
//    	log.info(com.sinopec.siam.utils.StringUtils.format("(doAuthentication) - 5.IDP登录页面的认证主题:[%s].", new Object[]{themeOfIdPLoginPage}));
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.3.4发送认证请求");
        sendAuthenticationRequest(httpRequest, httpResponse, handlerManager, requiredAuthenticationMethod, alwaysReauthen, themeOfIdPLoginPage);
        setRedirect_LAST_ACCESS_URL(httpRequest);

    }

    public static void setRedirect_LAST_ACCESS_URL(HttpServletRequest httpRequest){

        String redirect_last_access_url=httpRequest.getRequestURL().toString();
        String proxy_flag=SysConfig.siam_sp_proxy_flag;
        String siam_sp_proxy_load_url=SysConfig.siam_sp_proxy_load_url;
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>redirect_last_access_url="+redirect_last_access_url);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>负载或代理参数="+proxy_flag);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>siam_sp_proxy_load_url="+siam_sp_proxy_load_url);
        if("true".equals(proxy_flag)){
            redirect_last_access_url = siam_sp_proxy_load_url + httpRequest.getServletPath();
        }
        String querystring=httpRequest.getQueryString();
        if(querystring!=null){
            redirect_last_access_url+="?"+querystring;
        }
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>set HttpSession redirect_last_access_url="+redirect_last_access_url);
        SamlSession samlsession = ContextHolder.getContext();
        samlsession.setAttribute(ConfigConstants.NAME_OF_URL_RETURN_TO_APP, redirect_last_access_url);
        log.info(">>>>>AccessEnforcer>>>>>doAuthentication>>>>>3.3.3.5将认证前的请求路径和参数存入session 中"+ redirect_last_access_url);
    }

    /**
     * 调试用途，记录HttpRequest到日志中.
     * @param httpRequest
     */
    private void dumpToLog(HttpServletRequest httpRequest) {
        // Dump Header
        Enumeration names = httpRequest.getHeaderNames();
        while (names.hasMoreElements()) {
            String name = (String) names.nextElement();
            String value = httpRequest.getHeader(name);
            log.info(StringUtils.format("HTTP Header: [%s: %s]",new Object[]{ name, value}));
        }
        // Dump Request Parameter
        names = httpRequest.getParameterNames();
        while (names.hasMoreElements()) {
            String name = (String)names.nextElement();
            String[] values = httpRequest.getParameterValues(name);
            log.info(StringUtils.format("HTTP Req Param: [%s: %s]",new Object[]{ name, values}));
        }
    }

    /**
     * 判断是否需要探索IdP
     * @param httpRequest
     * @return
     */
    private static boolean needDiscoveryIdP(HttpServletRequest httpRequest) {

        return StringUtils.isEmpty(getIdpEntityID(httpRequest));
    }

    /**
     * @param httpRequest
     * @param httpResponse
     * @param handlerManager
     * @param requiredAuthenticationMethod
     * @param alwaysReauthen
     */
    public static void sendAuthenticationRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, SPProfileHandlerManager handlerManager,
                                                 String requiredAuthenticationMethod, boolean alwaysReauthen, String themeOfIdPLoginPage) {
        ProfileHandler profileHandler = handlerManager.getAuthenRequestHandler(httpRequest);
        log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>3.3.3.4.1获取认证请求处理器");
        AbstractErrorHandler errorHandler = handlerManager.getErrorHandler();
        log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>3.3.3.4.2获取异常处理器");
        HTTPInTransport profileReq = new HttpServletRequestAdapter(httpRequest);
        AuthenRequestContext authenRequestContext = new AuthenRequestContext();
        authenRequestContext.setAuthenticationMethod(requiredAuthenticationMethod);
        authenRequestContext.setForceAuthen(alwaysReauthen);
        authenRequestContext.setThemeOfIdPLoginPage(themeOfIdPLoginPage);
        log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>3.3.3.4.3构造请求上下文");
        httpRequest.setAttribute(ATTR_NAME_AUTHEN_REQUEST_CONTEXT, authenRequestContext);
        log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>3.3.3.4.4将上下文存入session中");
        HTTPOutTransport profileResp = new HttpServletResponseAdapter(httpResponse, httpRequest.isSecure());
        try {
            log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>3.3.3.4.5处理请求");
            profileHandler.processRequest(profileReq, profileResp);
        } catch (Exception e) {
            StringBuffer str =new StringBuffer();
            for(int i=0;i< e.getStackTrace().length;i++){
                str.append(e.getStackTrace()[i]);
            }
            httpRequest.setAttribute("SSOErrorMessage", "Metadata provider has not been initialized,please check configuration or log"+"\n"+str.toString());
            httpRequest.setAttribute(AbstractErrorHandler.ERROR_KEY, e);
            log.info(">>>>>AccessEnforcer>>>>>sendAuthenticationRequest>>>>>Failure to process SAML2 Authen request, cause: " + e.getMessage(), e);
            errorHandler.processRequest(profileReq, profileResp);
        }
    }

    /**
     * @param httpRequest
     * @param httpResponse
     * @param handlerManager
     */
    private static void sendDiscoveryIdPRequest(HttpServletRequest httpRequest, HttpServletResponse httpResponse, SPProfileHandlerManager handlerManager) {
        // Send SAML Discoveyr Request
        ProfileHandler profileHandler = handlerManager.getDiscoveryRequestHandler(httpRequest);
        HTTPInTransport profileReq = new HttpServletRequestAdapter(httpRequest);
        HTTPOutTransport profileResp = new HttpServletResponseAdapter(httpResponse, httpRequest.isSecure());
        try {
            profileHandler.processRequest(profileReq, profileResp);
        } catch (ProfileException e) {
            // throw new IOException(e);
            httpRequest.setAttribute(AbstractErrorHandler.ERROR_KEY, e);
            log.error("Failure to process SAML2 Discovery request, cause: " + e.getMessage(), e);
        }
        return;
    }

    /**
     * 获取会话认证级别
     *
     * @param httpRequest HTTPRequest请求对象
     * @return
     */
    private static String getRequiredAuthenLevel(String authenLevelHeaderName, HttpServletRequest httpRequest){
        if (authenLevelHeaderName == null) {
            return null;
        }
        String requiredAutheLevel = httpRequest.getParameter(authenLevelHeaderName);
        if (StringUtils.isEmpty(requiredAutheLevel)) {
            SamlSession samlsession = ContextHolder.getContext();
            if (samlsession.getAttribute(SSOPrincipal.LAST_REQUIRED_AUTHEN_LEVEL_OF_SESSION_ATTR) != null) {
                requiredAutheLevel = (String)samlsession.getAttribute(SSOPrincipal.LAST_REQUIRED_AUTHEN_LEVEL_OF_SESSION_ATTR);
                log.info(StringUtils.format("Get session authen level [%s]", new Object[]{requiredAutheLevel}));
            }
        }
        return requiredAutheLevel;
    }

    /**
     * 如果请求中包含了AUTHN_LEVEL的要求（目前是由WebSEAL EAI整合时，由WebSEAL提出的请求）在会话中暂存认证级别.
     *
     * @param httpRequest HTTPRequest请求对象
     * @return
     */
    private static void setRequiredAuthenLevel(String authenLevelHeaderName, HttpServletRequest httpRequest){
        String requiredAutheLevel = httpRequest.getParameter(authenLevelHeaderName);
        if (!StringUtils.isEmpty(requiredAutheLevel)) {
            SamlSession samlsession = ContextHolder.getContext();
            samlsession.setAttribute(SSOPrincipal.LAST_REQUIRED_AUTHEN_LEVEL_OF_SESSION_ATTR, requiredAutheLevel);

            log.info(StringUtils.format("Get request authen level [%s]", new Object[]{requiredAutheLevel}));
            log.info(StringUtils.format("Set session authen level [%s=%s]", new Object[]{SSOPrincipal.LAST_REQUIRED_AUTHEN_LEVEL_OF_SESSION_ATTR, requiredAutheLevel}));
        }
    }

    /**
     * 获取会话认证级别
     *
     * @param
     * @return
     */
    public static String getRequiredTheme(){
        // ApplicationContext applicationContext = WebApplicationContextUtils.getWebApplicationContext(filterConfig.getServletContext());
        ThemeDirector themeDirector = (ThemeDirector) WebApplicationContextUtils.getBean("siam.sp.themeDirector");
        //ThemeDirector themeDirector = (ThemeDirector)applicationContext.getBean("siam.sp.themeDirector", ThemeDirector.class);

        return themeDirector.getThemeOfIdPLoginPage();
    }

    /**
     * 如果请求中包含了Idp登录页面样式名称的要求，则在会话中暂存样式名称.
     *
     * @param httpRequest HTTPRequest请求对象
     * @return
     */
    private static void setRequiredTheme(String themeParamName, HttpServletRequest httpRequest){
        String themeOfIdpLoginPage = httpRequest.getParameter(themeParamName);
        if (!StringUtils.isEmpty(themeOfIdpLoginPage)) {
            SamlSession samlsession = ContextHolder.getContext();
            samlsession.setAttribute(ThemeDirector.THEME_ATTR_NAME, themeOfIdpLoginPage);

            log.info(StringUtils.format("Get request theme name [%s]", new Object[]{themeOfIdpLoginPage}));
            log.info(StringUtils.format("Set session theme name [%s=%s]", new Object[]{ThemeDirector.THEME_ATTR_NAME, themeOfIdpLoginPage}));
        }
    }

    /**
     * 获取请求认证方法
     * @param httpRequest HTTPRequest请求对象
     * @param authenLevelDirector 认证级别管理器
     * @return
     */
    private static String getRequiredAuthenMethod(HttpServletRequest httpRequest, AuthenLevelDirector authenLevelDirector, String defaultAuthenticationMethod, String authenLevelHeaderName) {
        String requiredAutheLevel = getRequiredAuthenLevel(authenLevelHeaderName, httpRequest);
        log.info("3.3.3.1获取认证等级:"+requiredAutheLevel);
        String requiredAuthenticationMethod = authenLevelDirector.getMatachedAuthenMethod(requiredAutheLevel);
        log.info("3.3.3.2根据认证等级获取认证方式:"+requiredAuthenticationMethod);
        if(requiredAuthenticationMethod == null || "".equals(requiredAuthenticationMethod)){
            // Use default authentication method
            requiredAuthenticationMethod = defaultAuthenticationMethod;
            log.info("如果认证方式为空，获取默认的认证方法"+requiredAuthenticationMethod);
        }
        log.info(StringUtils.format("Set authen method [%s]", new Object[]{requiredAuthenticationMethod}));
        return requiredAuthenticationMethod;
    }

    public static String getIdpEntityID(HttpServletRequest httpRequest) {
        // Otherwise load from session which set by DSResponse handler
        SamlSession samlsession = ContextHolder.getContext();
        return (String)samlsession.getAttribute(SAML2DiscoveryResponseHandler.ATTR_IDP_ENTITY_ID);
    }

    public static String getIdpEntityIdByCookie(HttpServletRequest request) {
        log.info("getIdpEntityIdByCookie");

        Cookie[] requestCookies = request.getCookies();
        if (requestCookies != null) {
            for (int i = 0; i < requestCookies.length; i++) {
                Cookie requestCookie = requestCookies[i];
                if (requestCookie != null && DatatypeHelper.safeEquals(requestCookie.getName(), SAML2DiscoveryResponseHandler.ATTR_IDP_ENTITY_ID)) {
                    return requestCookie.getValue();
                }
            }
        }
        return null;
    }

    public static void setIdpEntityID(HttpServletRequest request, HttpServletResponse response, String idpEntityID) {
        SamlSession samlsession = ContextHolder.getContext();

        samlsession.setAttribute(SAML2DiscoveryResponseHandler.ATTR_IDP_ENTITY_ID, idpEntityID);

        //String cookieDomain = getCookieDomain(context);

        Cookie contextKeyCookie = new Cookie(SAML2DiscoveryResponseHandler.ATTR_IDP_ENTITY_ID, idpEntityID);
        contextKeyCookie.setPath("".equals(request.getContextPath()) ? "/" : request.getContextPath());
        contextKeyCookie.setSecure(request.isSecure());
        if (cookieDomain != null) {
            contextKeyCookie.setDomain(cookieDomain);
        }
        response.addCookie(contextKeyCookie);
    }

    /**
     * 根据当前的应用本地的会话状态及认证的级别判断是否需要进行认证。
     * @param httpRequest
     * @param authenLevelDirector
     * @return
     */
    protected boolean needToAuthenticate(HttpServletRequest httpRequest, AuthenLevelDirector authenLevelDirector) {
        boolean authenticated = this.isAuthenticated(httpRequest);
        log.info(">>>>>AccessEnforcer>>>>>needToAuthenticate>>>>>判断当前用户是否已经认证："+authenticated);
        if(this.eaiAuthen){
            boolean matchWithRequiredAuthenLevel = this.isMatchWithRequiredAuthenLevel(httpRequest);
//      log.info(com.sinopec.siam.utils.StringUtils.format("(needToAuthenticate) - 1.是否需要认证: eaiAuthen=[%s], authenticated=[%s], matchAuthenLevel=[%s]", new Object[]{new Boolean(this.eaiAuthen), new Boolean(authenticated), new Boolean(matchWithRequiredAuthenLevel)}));
            log.info(">>>>>AccessEnforcer>>>>>needToAuthenticate>>>>>判断当前用户的认证级别是否满足被保护资源要求的认证方法：matchWithRequiredAuthenLevel="+matchWithRequiredAuthenLevel);
            return !authenticated || !matchWithRequiredAuthenLevel;
        }
        boolean matchWithRequiredAuthenMethod = isMatchWithRequiredAuthenMethod(httpRequest, authenLevelDirector);
        log.info(">>>>>AccessEnforcer>>>>>needToAuthenticate>>>>>判断当前用户的认证级别是否满足被保护资源要求的认证方法：matchWithRequiredAuthenMethod="+matchWithRequiredAuthenMethod);
//  	 	log.info(StringUtils.format("(needToAuthenticate) - 1.是否需要认证: eaiAuthen=[%s], authenticated=[%s], matchAuthenMethod=[%s]", new Object[]{new Boolean(this.eaiAuthen), new Boolean(authenticated), new Boolean(matchWithRequiredAuthenMethod)}));
        return !authenticated || !matchWithRequiredAuthenMethod;
    }

    /**
     * 判断当前用户是否已经认证
     *
     * @param httpRequest
     * @return true 已经认证
     */
    protected boolean isAuthenticated(HttpServletRequest httpRequest) {
        SamlSession samlsession = ContextHolder.getContext();
        log.info(">>>>>AccessEnforcer>>>>>isAuthenticated>>>>>samlsession="+samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR));
        if (samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR) != null) {
            return true;
        }
        User user = (User)httpRequest.getSession(true).getAttribute("weaver_user@bean");
        log.info(">>>>>AccessEnforcer>>>>>isAuthenticated>>>>>user="+user+",LOGIN_USERS="+LOGIN_USERS);
        if(user !=null && !"".equals(user)){
            log.info(">>>>>AccessEnforcer>>>>>isAuthenticated>>>>>user.getLoginid()="+user.getLoginid());
            if(LOGIN_USERS != null && LOGIN_USERS.contains(user.getLoginid())){
                return true;
            }
        }
        return false;
    }

    /**
     * 判断当前用户的认证级别是否满足被保护资源要求的认证方法。
     * @param httpRequest
     * @param authenLevelDirector
     * @return true - 满足要求
     */
    protected boolean isMatchWithRequiredAuthenMethod(HttpServletRequest httpRequest, AuthenLevelDirector authenLevelDirector) {
        SamlSession samlsession = ContextHolder.getContext();
        User user = (User)httpRequest.getSession(true).getAttribute("weaver_user@bean");
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>user="+user+",LOGIN_USERS="+LOGIN_USERS);
        if(user !=null && !"".equals(user)){
            log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>user.getLoginid()="+user.getLoginid());
            if(LOGIN_USERS != null && LOGIN_USERS.contains(user.getLoginid())){
                return true;
            }
        }
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>samlsession="+samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR));
        if (samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR) == null) {
            // Un-authen
            return false;
        }

        SSOPrincipal principal = (SSOPrincipal) samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR);
        String authenLevel = samlsession.getAttribute("authenLevel").toString();
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>authenLevel="+authenLevel);
        if(principal.containsAuthenMethod(authenLevel)){
            return true;
        }
        String requiredAutheLevel = authenLevelDirector.getAuthenLevel(authenLevel);
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>requiredAutheLevel="+requiredAutheLevel);
        String maxSucceedAuthenLevel = principal.getMaxSucceedAuthenLevel();
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenMethod>>>>>maxSucceedAuthenLevel="+maxSucceedAuthenLevel);
        return requiredAutheLevel.compareTo(maxSucceedAuthenLevel) <= 0;
    }

    /**
     * 判断当前用户的认证级别是否满足被保护资源要求的级别。
     *
     * @param httpRequest
     * @return true - 满足要求
     *
     */
    protected boolean isMatchWithRequiredAuthenLevel(HttpServletRequest httpRequest) {
        // Get AUTHEN_LEVEL sent by WebSEAL
        String requiredAutheLevel = httpRequest.getParameter(this.AUTHN_LEVEL_FIELD);
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenLevel>>>>>requiredAutheLevel="+requiredAutheLevel);
        if (StringUtils.isEmpty(requiredAutheLevel)){
            return true;
        }

        SamlSession samlsession = ContextHolder.getContext();

        SSOPrincipal principal = (SSOPrincipal)samlsession.getAttribute(SSOPrincipal.NAME_OF_SESSION_ATTR);
        if (principal == null) {
            return false;
        }
        String maxSucceedAuthenLevel = principal.getMaxSucceedAuthenLevel();
        log.info(">>>>>AccessEnforcer>>>>>isMatchWithRequiredAuthenLevel>>>>>maxSucceedAuthenLevel="+maxSucceedAuthenLevel);
        return requiredAutheLevel.compareTo(maxSucceedAuthenLevel) <= 0;
    }

    /**
     * @param httpRequest
     * @return
     */
    private boolean isSPHandlerRequest(HttpServletRequest httpRequest) {
        String uri = httpRequest.getRequestURI().toString();
        log.info(">>>>>AccessEnforcer>>>>>isSPHandlerRequest>>>>>uri="+uri);
        String contextPath = httpRequest.getContextPath();
        log.info(">>>>>AccessEnforcer>>>>>isSPHandlerRequest>>>>>contextPath="+contextPath);
        String requestPath = uri;
        if (!contextPath.equals("/")) {
            requestPath = requestPath.substring(contextPath.length());
        }
        log.info(">>>>>AccessEnforcer>>>>>isSPHandlerRequest>>>>>requestPath="+requestPath);
        return requestPath.startsWith("/SSO");
    }

    /**
     * @param httpRequest
     * @return
     */
    private boolean isNoFilterURLRequest(HttpServletRequest httpRequest) {
        String uri = httpRequest.getRequestURI().toString();
        log.info(">>>>>AccessEnforcer>>>>>isNoFilterURLRequest>>>>>uri="+uri);
        String contextPath = httpRequest.getContextPath();
        log.info(">>>>>AccessEnforcer>>>>>isNoFilterURLRequest>>>>>contextPath="+contextPath);
        String requestPath = uri;
        if (!contextPath.equals("/")) {
            requestPath = requestPath.substring(contextPath.length());
        }
        log.info(">>>>>AccessEnforcer>>>>>isNoFilterURLRequest>>>>>requestPath="+requestPath);
        if(this.noFilterURLs == null){
            return false;
        }
        log.info(">>>>>AccessEnforcer>>>>>isNoFilterURLRequest>>>>>noFilterURLs="+noFilterURLs.toString());
        for (int i = 0; i < noFilterURLs.length; i++) {
            String noFilterURL = noFilterURLs[i];
            if(noFilterURL.endsWith("*")){
                noFilterURL = noFilterURL.substring(0, noFilterURL.lastIndexOf("*"));
                if(requestPath.startsWith(noFilterURL)){
                    return true;
                }
            }
            if(requestPath.equals(noFilterURL)){
                return true;
            }
        }
        return false;
    }

    /**
     * @param httpRequest
     * @return
     */
    private boolean isFilterURLRequest(HttpServletRequest httpRequest,String[] filterURLs) {
        String uri = httpRequest.getRequestURI().toString();
        String contextPath = httpRequest.getContextPath();
        String requestPath = uri;
        if (!contextPath.equals("/")) {
            requestPath = requestPath.substring(contextPath.length());
        }
        if(filterURLs == null){
            return false;
        }
        for (int i = 0; i < filterURLs.length; i++) {
            String filterURL = filterURLs[i];
            if(filterURL.endsWith("*")){
                filterURL = filterURL.substring(0, filterURL.lastIndexOf("*"));
                if(requestPath.startsWith(filterURL)){
                    return true;
                }
            }
            if(requestPath.equals(filterURL)){
                return true;
            }
        }
        return false;
    }
}
